import {FAKE_BASE_PATH, FAKE_METHOD, FAKE_PATH, FAKE_INJECTABLE_WATERMARK} from './common/const'
import express, { Express } from 'express'

class FakeFactory {
    private readonly app: Express
    private readonly types: Record<string, any>

    constructor(){
        // 实例化Express
        this.app = express()
        // types将缓存所有Provider，保证其只被实例化一次
        this.types = {}
    }

    // 调用该方法以注册所需信息至Express实例并返回
    create(module: any): Express{
        // 获取在module装饰器中定义的Controller数组
        const Controllers =  Reflect.getMetadata("controllers",module)
        this.initControllers(Controllers)
        // 返回Express实例
        return this.app
    }

    // 初始化所有Controllers
    initControllers(Controllers: any[]){
        Controllers.forEach((Controller:any) =>{
            // module中的controller数组中每个controller的构造函数的参数的类型
            const paramTypes = Reflect.getMetadata("design:paramtypes",Controller)
            const args = paramTypes.map((Type:any) =>{
                // 根据Controller构造函数中依赖的参数进行遍历
                // 如果他所依赖的那个参数不是可以Injectable的则直接报错
                // 否则从缓存中拿去这个依赖的实例，如果没有则创建
                if(!Reflect.getMetadata(FAKE_INJECTABLE_WATERMARK,Type)){
                    throw new Error(`${Type.name} is not injectable!`)
                }else{
                    return this.types[Type.name]?this.types[Type.name]:this.types[Type.name] = new Type()
                }
            })
            // 根据创建出来的依赖建立一个Controller的实例
            const controller = new Controller(...args)
            // 获取Controller的路由根路径
            const path = Reflect.getMetadata(FAKE_BASE_PATH,Controller)

            this.initRoute(controller,path)
        })
    }

    // 初始化一个controller实例上所有的监听方法
    initRoute(controller: any, basePath: string) {
        // 获取controller实例原型上的所有属性
       const proto = Object.getPrototypeOf(controller)
        if(!proto) return
        // 过滤出除了构造函数之外的方法名称
        const methodsNames = Object.getOwnPropertyNames(proto).filter((item) =>{
            return item !== "constructor" && typeof proto[item] === "function"
        })

        // 遍历这些方法名称，取出在元数据上定义的方法名称或路由的路径
        methodsNames.forEach(methodName =>{
            const fn = proto[methodName]

            const methods = Reflect.getMetadata(FAKE_METHOD,controller,methodName)
            const path = Reflect.getMetadata(FAKE_PATH,controller,methodName)

            if(!methods || !path) return
            // 创建一个express的route对象
            const route = {
                path: basePath + path,
                method: methods.toLowerCase(),
                fn: fn.bind(controller)
            }
            // 创建路由
            this.registerRoute(route)
        })

    }

    // 将Http监听方法注册至Express实例之中
    registerRoute(route: {path: string, method: string, fn: Function}){
        const {path, method, fn} = route
        // Express实例上注册路由
        // @ts-ignore
        this.app[method](path, (req: any, res: any) => {
            res.send(fn(req))
        })

    }
}

export default new FakeFactory()
